package som;

import java.util.AbstractMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map.Entry;

/**
 * Class represents a cell on the SOM.
 * Each cell has a representing sample, which is changed throughout the training, and a list
 * of assigned samples
 */
public class Cell {
	
	private int row,column; // cell's position on the SOM grid
	private Sample representingExample; // cell's representing sample
	private List<Sample> samples; // assigned samples to this cell
	private List<Integer> examplesClassificationsList; // classifications of the assigned samples
	private Board board; // the SOM grid	
	public static final int ERROR_VALUE = -1; // indicates that no samples are assigned to this cell
	
	/**
	 * Constructor- creates a new cell
	 * @param x cell's row index on the SOM grid
	 * @param y cell's column index on the SOM grid
	 * @param board SOM's grid
	 */
	public Cell(int x, int y, Board board)
	{
		row = x;
		column = y;
		representingExample = null;
		examplesClassificationsList = new LinkedList<Integer>();
		samples = new LinkedList<Sample>();		
		this.board = board;
	}
	
	/**
	 * 
	 * @param exampleToAssign new example to be assigned to this cell or to one of its neighbors
	 * @param learningRate learning rate for the update rule
	 * @param neighborhoodRate rate according to cell's proximity to the cell being updated (could
	 * be this cell or a neighboring cell)
	 */
	public void updateRepresentingExam(Sample exampleToAssign, double learningRate, double neighborhoodRate)
	{
		// update representing example according to udpate rule
		representingExample.updateValues(exampleToAssign, learningRate, neighborhoodRate);
	}

	/**
	 * Assign given example to this cell
	 * @param newSample example to assign
	 */
	public void assignSample(Sample newSample) {
		samples.add(newSample);
		examplesClassificationsList.add(newSample.getClassification());
	}
	
	/**
	 * Return cell's representing example
	 * @return cell's representing example
	 */
	public Sample getRepresentingExample() {
		return representingExample;
	}

	/**
	 * Set a representing example for the cell
	 * @param newExample new representing example
	 */
	public void setRepresentingExample(Sample newExample) {		
		this.representingExample = newExample;				
	}

	/**
	 * Returns classifications of the examples assigned to this cell
	 * @return classifications of the examples assigned to this cell
	 */
	public List<Integer> getExamplesClassifications() {
		return examplesClassificationsList;
	}

	/**
	 * Returns cell's row index on the SOM grid
	 * @return cell's row index on the SOM grid
	 */
	public int getRow() {
		return row;
	}

	/**
	 * Returns cell's column index on the SOM grid
	 * @return cell's column index on the SOM grid
	 */
	public int getColumn() {
		return column;
	}
	
	/**
	 * Finds the most prominent classification among all of the
	 * examples assigned to this cell.
	 * If there is a tie, decides according to the prominent
	 * classification among the cell's 8-type neighbors (neighborhood
	 * of distance = 1)
	 * @return most prominent classification among all of the
	 * examples assigned to this cell
	 */
	public double getMostProminentClassification()
	{	
		// if empty, return error value
		if (examplesClassificationsList.isEmpty()) {
			return ERROR_VALUE;
		}

		// count instances of each classification among the assigned examples
		float[] counters = {0,0,0,0,0,0,0,0,0,0};
		for(int cls : examplesClassificationsList)
		{
			counters[cls]++;
		}
		
		// get most prominent and second-most-prominent classifications
		Entry<Integer, Integer> prominents = findTwoMaxValues(counters);
		
		// in case of ties, consider neighboring cells
		if (counters[prominents.getKey()] == counters[prominents.getValue()]) {
			return mostProminentInNeighborhood(prominents.getKey(), prominents.getValue());
		}
		
		// only one most prominent classification, return it
		return prominents.getKey();
	}

	/**
	 * Gets an array and returns the indices of the two maximal values
	 * @param array array to search in
	 * @return a pair where first element is index of maximal array entry
	 * and second element is index of second maximal array entry
	 */
	private Entry<Integer,Integer> findTwoMaxValues(float[] array) {
		
		int max = 0, secondMax = 1;
		for (int i = 1 ; i < 10 ; ++i) { // scan counters array
			
			// update index of maximal value
			if (array[max] < array[i]) {
				max = i;
				continue;
			}
			
			// if current array entry cannot be maximal value, check if is second greatest value
			if (array[secondMax] <= array[i]) {
				secondMax = i;
			}
		}

		// return found values		
		return new AbstractMap.SimpleEntry<Integer,Integer>(max, secondMax);
	}
	
	/**
	 * Gets two classifications and returns the one which is more prominent
	 * in the neighborhood of the cell.
	 * If there is a tie among the neighbors, returns the first classification
	 * @param firstProminent first prominent classification among cell's assigned examples
	 * @param secondProminent second prominent classification among cell's assigned examples
	 * @return the classification (among the two given) which is more prominent in the neighborhood
	 * of the cell
	 */
	private int mostProminentInNeighborhood(int firstProminent, int secondProminent) {
	
		// get first circle of neighboring cells
		List<Cell> neighbors = board.getNeighborhoods(1, this);
		
		// count instances of each given classification among the neighbors' prominent values
		int firstCtr = 0, secondCtr = 0;
		for (Cell neighbor : neighbors) {
			
			// get prominent classification among current neighbor's assigned samples only
			int neighborProminent = neighbor.getProminentNoNeighbors();
			
			// update matching counter
			if (neighborProminent == firstProminent) {
				++firstCtr;
				continue;
			}			
			if (neighborProminent == secondProminent) {
				++secondCtr;
				continue;
			}
		}
		
		// return prominent classification among the neighboring cells
		return (firstCtr >= secondCtr) ? firstProminent : secondProminent;
	}

	/**
	 * Finds the most prominent classification among all of the
	 * examples assigned to this cell.
	 * If there is a tie, returns the first maximal value (does
	 * not consider the neighbors)
	 * @return most prominent classification among all of the
	 * examples assigned to this cell
	 */
	private int getProminentNoNeighbors() {

		// count instances of each classification among the assigned examples
		float[] counters = {0,0,0,0,0,0,0,0,0,0};
		for(int cls : examplesClassificationsList)
		{
			counters[cls]++;
		}

		// find most prominent classification among the assigned examples
		int max = 0;
		for (int i = 1 ; i < 10 ; ++i) {
			max = max >= counters[i] ? max : i;
		}
		
		// return classification matching maximal counter
		return max;
	}
	
	/**
	 * Returns a string containing the classification
	 * of each assigned examples
	 * @return concatenated classifications of the assigned examples 
	 */
	public String getClassificationsOfAssignedSamples()
	{
		String str = "";
		for(Integer i : examplesClassificationsList)
		{
			str += " " + i;
		}
		return str;
	}
	
	/**
	 * Returns a list of cell's assigned examples
	 * @return cell's assigned examples
	 */
	public List<Sample> getAssignedSamples() {
		return samples;
	}
}
